﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using System.Threading;

namespace CommLink
{
    class SerialPortComm : CommLinkAbstract
    {
        /// <summary>
        /// Pipe data transfer length constant.
        /// </summary>
        const int nPipeDataLen = 4096;
        
        /// <summary>
        /// Serial communication port
        /// </summary>
        SerialPort nSerialCommPort;

        /// <summary>
        /// Serial communication port
        /// </summary>
        Semaphore nSCPReadBlock;
       
        /// <summary>
        /// Public constructor
        /// </summary>
        public SerialPortComm()
        {
        }

        /// <summary>
        /// Destructor closing the named pipe
        /// </summary>
        ~SerialPortComm()
        {
            closeSerialPorts();
        }
        
        /// <summary>
        /// Closes up anyform of open comm channels
        /// </summary>
        public override void closeConnection()
        {
            closeSerialPorts();
        }

        /// <summary>
        /// Named pipes get shut down.
        /// </summary>
        public void closeSerialPorts()
        {
            terminating = true;

            if (nSerialCommPort != null && nSerialCommPort.IsOpen)
                nSerialCommPort.Close();

            if (OnConnectionStatusChange != null)
                OnConnectionStatusChange(ConnectionStates.NotConnected);
        }

        /// <summary>
        /// Sends a data buffer trough the named pipe to the CCS integrated dll
        /// </summary>
        /// <param name="OutData">Byte array to sned</param>
        /// <returns>Error status</returns>
        public override int messageSend(byte[] OutData)
        {
            int status = 0;
            try
            {
                if (OutData.Length > nPipeDataLen)
                {
                    MessageBox.Show("Message exceeds pipe limit of " + nPipeDataLen.ToString());
                    status = -1;
                }
                else
                {
                    int transmitChunkSize = 64;
                    int totalData2Send = OutData.Length;
                    int sendDataCounter = 0;
                    while (totalData2Send > 0)
                    {
                        if (totalData2Send > transmitChunkSize)
                        {

                            byte[] tmpbuff = new byte[transmitChunkSize];
                            for (int ikc = 0; ikc < transmitChunkSize; ikc++)
                            {
                                tmpbuff[ikc] = OutData[sendDataCounter++];
                            }
                            nSerialCommPort.Write(tmpbuff, 0, transmitChunkSize);
                            totalData2Send -= transmitChunkSize;
                        }
                        else
                        {
                            byte[] tmpbuff = new byte[totalData2Send];
                            for (int ikc = 0; ikc < totalData2Send; ikc++)
                            {
                                tmpbuff[ikc] = OutData[sendDataCounter++];
                            }
                            nSerialCommPort.Write(tmpbuff, 0, totalData2Send);
                            totalData2Send = 0;
                        }
                        Thread.Sleep(300);
                    }
                    //Complete buffer sending
                    //nSerialCommPort.Write(OutData, 0, OutData.Length);
                }
            }
            catch (Exception ex)
            {
                status = -1;
                MessageBox.Show(ex.Message);
            }

            if (nSerialCommPort.IsOpen && (status == -1 || terminating))
            {
                closeSerialPorts();
                MessageBox.Show("Server connection lost");
            }

            return status;
        }

        /// <summary>
        /// Pipe reading thread termination flag.
        /// </summary>
        Boolean terminating = false;

        /// <summary>
        /// Pipe reading thread funciton.
        /// </summary>
        void threadReadingFunction()
        {
            while (!terminating && nSerialCommPort.IsOpen)
            {
                byte[] msgHeader = comPortReadBytes(12);
                if (msgHeader == null)
                    break;

                int msgPayloadSize = 0;
                int strmPos = 8;
                for (int i = 0; i < 4; i++)
                    msgPayloadSize |= (int)(msgHeader[strmPos++] << (8 * i));

                if (msgPayloadSize <= 0 || msgPayloadSize > 4096)
                {
                    MessageBox.Show("Message payload size of " + msgPayloadSize.ToString() + " received. Discarding message.");
                    continue;
                }

                byte[] msgPayload = comPortReadBytes(msgPayloadSize);
                if (msgPayload == null)
                    break;

                byte[] msgInc = new byte[msgHeader.Length + msgPayload.Length];
                msgHeader.CopyTo(msgInc, 0);
                msgPayload.CopyTo(msgInc, msgHeader.Length);

                if (OnMessageReceived != null)
                    OnMessageReceived(msgInc);
            }
            if (!terminating)
                MessageBox.Show("Server connection lost");

            if (nSerialCommPort.IsOpen)
                closeSerialPorts();
        }

        byte[] comPortReadBytes(int numBytesRead)
        {
            int readBufPos = 0;
            byte[] msgInc = new byte[numBytesRead];

            do
            {
                while (nSerialCommPort.IsOpen && nSerialCommPort.BytesToRead == 0)
                {
                    nSCPReadBlock.WaitOne(500);

                    if (!nSerialCommPort.IsOpen)
                        return null;
                }

                do
                {
                    if (!nSerialCommPort.IsOpen)
                        return null;

                    int toRead = numBytesRead - readBufPos;
                    if (nSerialCommPort.BytesToRead < toRead)
                        toRead = nSerialCommPort.BytesToRead;

                    nSerialCommPort.Read(msgInc, readBufPos, toRead);
                    readBufPos += toRead;
                } while (nSerialCommPort.BytesToRead > 0 && readBufPos < numBytesRead);
            } while (readBufPos < numBytesRead);

            return msgInc;
        }

        /// <summary>
        /// Message received event providing the byte array data received from the pipe.
        /// </summary>
        public override event MessageReceived OnMessageReceived;

        /// <summary>
        /// Connection status change event.
        /// </summary>
        public override event ConnectionStatusChange OnConnectionStatusChange;

        /// <summary>
        /// Opens communication channels
        /// </summary>
        public override void openConnection()
        {
            bool connectFailed = false;

            try
            {
                string port_name = Properties.Settings.Default.CommPort_PortName;
                if (port_name == "NO_PORT_DEFINED")
                {
                    nSerialCommPort = new SerialPort();
                    Properties.Settings.Default.CommPort_PortName = nSerialCommPort.PortName;
                    Properties.Settings.Default.CommPort_BaudRate = nSerialCommPort.BaudRate;
                    Properties.Settings.Default.CommPort_ParityBits = (int)nSerialCommPort.Parity;
                    Properties.Settings.Default.CommPort_DataBits = nSerialCommPort.DataBits;
                    Properties.Settings.Default.CommPort_StopBits = (int)nSerialCommPort.StopBits;
                    Properties.Settings.Default.Save();
                }
                else
                {
                    int baud_rate = Properties.Settings.Default.CommPort_BaudRate;
                    Parity parityBit = (Parity)Properties.Settings.Default.CommPort_ParityBits;
                    int data_bits = Properties.Settings.Default.CommPort_DataBits;
                    StopBits stopBits = (StopBits)Properties.Settings.Default.CommPort_StopBits;

                    nSerialCommPort = new SerialPort(port_name, baud_rate, parityBit, data_bits, stopBits);
                }
                nSerialCommPort.Open();
                nSerialCommPort.DiscardOutBuffer();
                nSerialCommPort.DiscardInBuffer();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to open serial port.\n" + ex.Message);
                connectFailed = true;
            }

            if (!connectFailed)
            {
                try
                {
                    nSCPReadBlock = new Semaphore(0, 100);
                    nSerialCommPort.DataReceived += new SerialDataReceivedEventHandler(nSCPSend_DataReceived);

                    Thread threadReading = new Thread(new ThreadStart(threadReadingFunction));
                    threadReading.IsBackground = true;
                    threadReading.Start();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Failed to open serial port.\n" + ex.Message);
                    connectFailed = true;
                }
            }

            if (OnConnectionStatusChange != null)
            {
                if (connectFailed)
                    OnConnectionStatusChange(ConnectionStates.ServerError);
                else
                    OnConnectionStatusChange(ConnectionStates.Connected);
            }
        }

        void nSCPSend_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            nSCPReadBlock.Release();
        }

        /// <summary>
        /// Returns connection type name
        /// </summary>
        /// <returns>Connection type name</returns>
        public override string getName()
        {
            return "serial communication port";
        }
    }
}
